package com.googlecode.sparda.commons.lang;

/**
 * Class that provide utility for object tabulation.
 * This class can do tabled string starting from an objects array.
 * @author Fabio Grucci (<i>alias</i> <b>Phoenix</b>)
 * @version 1.0
 * @since 1.0
 *
 */
public class StringTabulator {

	private static final int DEFAULT_TAB_SIZE = 8;

	private static final CharSequence DEFAULT_NULL_VALUE = "null";
	
	private final int tabSize;

	private final CharSequence nullValue;
	
	private final boolean workWithNormalizedData;
	
	public StringTabulator() {
		this(DEFAULT_TAB_SIZE, DEFAULT_NULL_VALUE);
	}
	
	public StringTabulator(final CharSequence nullValue) {
		this(DEFAULT_TAB_SIZE, nullValue);
	}
	
	public StringTabulator(final int tabSize) {
		this(tabSize, DEFAULT_NULL_VALUE);
	}
	
	public StringTabulator(final int tabSize, final CharSequence nullValue) {
		this(tabSize, nullValue, false);
	}
	
	public StringTabulator(final int tabSize, final CharSequence nullValue, boolean workWithNormalizedData) {
		if(nullValue == null)
			throw new IllegalArgumentException("CharSequence Nullvalue cannot be null.");
		this.tabSize = tabSize;
		this.nullValue = nullValue;
		this.workWithNormalizedData = workWithNormalizedData;
	}
	
	public String renderAsTabbedTable(final CharSequence[]... strings) {
		return this.renderAsTabbedTable((Object[][]) strings);
	}
	
	public String renderAsTabbedTable(final Object[]... objects) {
		if(objects.length == 0)
			return "";
		final int[] maxColumnLength = new int[getMaxColumnLengthArray(objects)];
		try {
			initMaxColumnLength(maxColumnLength, objects);
		} catch (ArrayIndexOutOfBoundsException e) {
			if(this.workWithNormalizedData)
				throw new UnexpectedException(e);
			throw new IllegalStateException("Cannot init MaxColumnLength for this input, maybe you must set #workWithNormalizedData to false.", e);
		}
		
		final StringBuilder tableString = new StringBuilder();
		for (int i = 0; i < objects.length; i++) {
			for (int j = 0; j < objects[i].length; j++) {
				final CharSequence objectCS = this.getObjectCharSequence(objects[i][j], i, j);
				tableString.append(objectCS);
				this.addTab(tableString, objectCS, maxColumnLength, j);
			}
			tableString.append("\n");
		}
		if(tableString.length() > 0)
			tableString.deleteCharAt(tableString.length() - 1);
		
		return tableString.toString();
	}

	protected void initMaxColumnLength(final int[] maxColumnLength,
			final Object[]... objects) {
		for (int i = 0; i < objects.length; i++) {
			for (int j = 0; j < objects[i].length; j++) {
				final CharSequence objectCS = this.getObjectCharSequence(objects[i][j], i, j);
				final int size = objectCS.length();
				if(maxColumnLength[j] < size)
					maxColumnLength[j] = size;
			}
		}
	}

	protected int getMaxColumnLengthArray(final Object[]... objects) {
		if(this.workWithNormalizedData)
			return objects[0].length;
		int maxColumnNumber = 0;
		for (final Object[] objectsRow : objects)
			if(objectsRow.length > maxColumnNumber)
				maxColumnNumber = objectsRow.length;
		return maxColumnNumber;
	}

	/**
	 * Method to transform object instance in {@link CharSequence} representation.
	 * @param object The object to transform
	 * @param row The row where the object is located
	 * @param column The row where the object is located
	 * @return The {@link CharSequence} representing the object
	 */
	protected CharSequence getObjectCharSequence(final Object object, final int row, final int column) {
		if(object == null)
			return this.nullValue;
		return object.toString();
	}

	private void addTab(final StringBuilder tableString, final CharSequence objectCS, final int[] maxColumnLength, final int columnIndex) {
		if(maxColumnLength.length == columnIndex + 1)
			return;
		for (int tabCount = 1 + maxColumnLength[columnIndex]/this.tabSize - objectCS.length()/this.tabSize; 
				tabCount > 0;
				tabCount--)
			tableString.append("\t");
	}
	
}
